# 合约监控

import pandas as pd
import numpy as np
from datetime import datetime
from WindPy import w
w.start()
#这个函数需要利用wind，有公司的数据库应该不需要这个函数
def get_mrk_prc(beg_date:str,end_date:str,underlying_list:list):
    """获取指定标的，指定日期下的收盘价
    :param beg_date: 计算相关性的起始日期
    :type beg_date: str
    :param trade_date: _description_
    :type trade_date: str
    :underlying_list: 标的列表
    :type underlying_list: list    
    """
    
    
    a=w.wsd(underlying_list,"close",beg_date,end_date)
    if(beg_date!=end_date):
        price=pd.DataFrame(data=a.Data,index=a.Codes,columns=a.Times).T
        df_new=pd.DataFrame()
        for code in price.columns:
            df_temp=pd.DataFrame()
            df_temp['TRADE_DATE']=price[code].index
            df_temp['UNDERLYING']=[code]*len(df_temp)
            df_temp['UNDERLYING_PRICE']=list(price[code])
            df_new=df_new.append(df_temp)

    else:
        df_new=pd.DataFrame()
        df_new['UNDERLYING']=a.Codes
        df_new['UNDERLYING_PRICE']=a.Data[0]
        df_new['TRADE_DATE']=a.Times*len(df_new)
        df_new=df_new[['TRADE_DATE','UNDERLYING','UNDERLYING_PRICE']]
        
    df_new['TRADE_DATE']=df_new['TRADE_DATE'].apply(lambda x:datetime.strftime(x,'%Y-%m-%d'))    
    return df_new




def get_portfolio_ls_requirement_summary(beg_date:str,trade_date:str, portfolio:pd.DataFrame) -> pd.DataFrame:
    """汇总基于互换新规的计算结果，判断持仓数据是否满足各个条件及详情
    
    :param beg_date: 计算相关性的起始日期
    :type beg_date: str
    :param trade_date: 持仓日期
    :type trade_date: str
    :param portfolio: 持仓数据
    :type portfolio: pd.DataFrame
    :return: 返回结果，符合互换新规输出success，否则输出not success以及不符合的条件
    :rtype: pd.DataFrame
    """

    if('COST' in list(portfolio)):
        pass
    else:
        underlying_list=list(portfolio['UNDERLYING'])
        underlying_price=get_mrk_prc(trade_date,trade_date,underlying_list)
        portfolio=pd.merge(portfolio,underlying_price,on=['TRADE_DATE','UNDERLYING'])
        portfolio['COST']=portfolio['QUANTITY']*portfolio['UNDERLYING_PRICE']
     
    portfolio_left=portfolio[portfolio['DIRECTION']=='L']
    portfolio_right=portfolio[portfolio['DIRECTION']=='R']
    
    
    #前半部分在判断是否符合第二类保证金的要求
    #如果符合返回一个dataframe，输出最低保证金   

    if(get_portfolio_ls_requirement_underlying_count(portfolio)['number'].max()>=50 
       and get_portfolio_ls_requirement_cost_weight(trade_date,portfolio)['COST_WEIGHT'].max()<=0.05 
       and get_portfolio_ls_requirement_corr(beg_date,trade_date,portfolio)>=0.8
       and get_portfolio_ls_requirement_net_exposure(trade_date,portfolio)>=0.8
       and get_portfolio_ls_requirement_net_exposure(trade_date,portfolio)<=1.2  ):
         
        min_margin=max(portfolio_left['COST'].sum(),portfolio_right['COST'].sum())*0.25
        
        return pd.DataFrame(data=['success',min_margin],index=['result','min_margin'])
    
    else:
        #后半部分判断如果不符合第二类保证金，问题出在哪里，返回一个dataframe记录不符合条件的地方以及数值
        
        condition_list=['not success']
        result_list=['result']
        if(get_portfolio_ls_requirement_underlying_count(portfolio)['number'].max()<50):
            condition_list.append('underlying_count_not_requirement')
            result_list.append(get_portfolio_ls_requirement_underlying_count(portfolio)['number'].max())
            
        if(get_portfolio_ls_requirement_cost_weight(trade_date,portfolio)['COST_WEIGHT'].max()>0.05):
            condition_list.append('cost_weight_not_requirement')
            result_list.append(get_portfolio_ls_requirement_cost_weight(trade_date,portfolio)['COST_WEIGHT'].max())
            
        if(get_portfolio_ls_requirement_corr(beg_date,trade_date,portfolio)<0.8):
            condition_list.append('corr_not_requirement')
            result_list.append(get_portfolio_ls_requirement_corr(beg_date,trade_date,portfolio))
            
        if(get_portfolio_ls_requirement_net_exposure(trade_date,portfolio)<0.8 
           or get_portfolio_ls_requirement_net_exposure(trade_date,portfolio)>1.2):
            condition_list.append('net_exposure_ont_requirement')
            result_list.append(get_portfolio_ls_requirement_net_exposure(trade_date,portfolio))
            
        return pd.DataFrame(data=result_list,index=condition_list)
       
    

def get_portfolio_ls_requirement_underlying_count(portfolio:pd.DataFrame) -> float:
    """根据互换新规，计算持仓数据的挂钩股票数量

    :param portfolio: 持仓数据
    :type portfolio: pd.DataFrame
    :return: 计算结果
    :rtype: pd.DataFrame
    """
    #check
    
    left_number=len(portfolio[portfolio['DIRECTION']=='L'])
    right_number=len(portfolio[portfolio['DIRECTION']=='R'])
    #这里面默认持仓数据没有重复
    return pd.DataFrame(data=[left_number,right_number],index=['L','R'],columns=['number'])
    

def get_portfolio_ls_requirement_cost_weight(trade_date, portfolio:pd.DataFrame) -> pd.DataFrame:
    """根据互换新规，计算持仓数据的名义本金占比

    :param trade_date: 持仓日期
    :type trade_date: str
    :param portfolio: 持仓数据
    :type portfolio: pd.DataFrame
    :return: 计算结果
    :rtype: pd.DataFrame
    """
    #check
    
        #先判断客户数据是否给了COST，即成本
    if('COST' in list(portfolio)):
        pass
    else:
        #如果客户没给成本，则用当天的价格计算，先提取今日价格，再合并表格
        underlying_list=list(portfolio['UNDERLYING'])
        underlying_price=get_mrk_prc(trade_date,trade_date,underlying_list)
        portfolio=pd.merge(portfolio,underlying_price,on=['TRADE_DATE','UNDERLYING'])
        portfolio['COST']=portfolio['QUANTITY']*portfolio['UNDERLYING_PRICE']
    
    portfolio_left=portfolio[portfolio['DIRECTION']=='L']
    portfolio_left['COST_WEIGHT']=portfolio_left['COST']/portfolio_left['COST'].sum()
    portfolio_right=portfolio[portfolio['DIRECTION']=='R']
    portfolio_right['COST_WEIGHT']=portfolio_right['COST']/portfolio_right['COST'].sum()
    left_weight=portfolio_left[['UNDERLYING','COST_WEIGHT']]
    right_weight=portfolio_right[['UNDERLYING','COST_WEIGHT']]
    weight=left_weight.append(right_weight)
    return weight

def get_portfolio_ls_requirement_corr(beg_date:str, trade_date:str,portfolio:pd.DataFrame) -> float:
    """根据互换新规，计算持仓数据的多空相关性
    
    :param beg_date: 计算相关性的起始日期
    :type beg_date: str
    :param trade_date: 持仓日期
    :type trade_date: str
    :param portfolio: 持仓数据
    :type portfolio: pd.DataFrame
    :return: 多空相关性
    :rtype: float
    """
    
    #需要获取交易日有多少天，来判断是否有足够多的数据
    from open_utils_lite.calendar_utils import date_range
    trading_days=date_range(beg_date,trade_date)
    
    #对left方向做处理，先筛选掉一年内没数据的票
    left_portfolio=portfolio[portfolio['DIRECTION']=='L']
    left_underlying=list(left_portfolio['UNDERLYING'])
    
    left_return=pd.DataFrame()
    modified_left_list=[]
    for underlying in left_underlying:
        temp_price=get_mrk_prc(beg_date,trade_date,[underlying])
        if(len(temp_price)==trading_days):
            #这里在判断是否有上市时间过近的标的，如果交易数据不够，则剔除该标的
            temp_price['LOG_PRICE']=np.log(temp_price['UNDERLYING_PRICE'])
            temp_price['RETURN']=temp_price['LOG_PRICE']-temp_price['LOG_PRICE'].shift(1)
            left_return=left_return.append(temp_price)
            modified_left_list.append(underlying)
            #将符合条件的标的存入新的list
    portfolio_modified_left=left_portfolio[left_portfolio['UNDERLYING'].isin(modified_left_list)]
    left_price=get_mrk_prc(trade_date,trade_date,modified_left_list)
    portfolio_modified_left=pd.merge( portfolio_modified_left,left_price,on=['TRADE_DATE','UNDERLYING'])
    
    #计算weight
    left_total=(portfolio_modified_left['QUANTITY']*portfolio_modified_left['UNDERLYING_PRICE']).sum()
    portfolio_modified_left['WEIGHT']=portfolio_modified_left['QUANTITY']*portfolio_modified_left['UNDERLYING_PRICE']/left_total
    left_weight= portfolio_modified_left[['UNDERLYING','WEIGHT']]
    
    time_list=list(left_return['TRADE_DATE'].drop_duplicates())
    left_return_list=[]
    for time in time_list:
        left_temp=left_return[left_return['TRADE_DATE']==time]
        left_temp=pd.merge(left_temp,left_weight,on='UNDERLYING')
        left_return_list.append((left_temp['RETURN']*left_temp['WEIGHT']).sum())


       
    #对right方向做处理，先筛选掉一年内没数据的票
    right_portfolio=portfolio[portfolio['DIRECTION']=='L']
    right_underlying=list(right_portfolio['UNDERLYING'])
    
    right_return=pd.DataFrame()
    modified_right_list=[]
    for underlying in right_underlying:
        temp_price=get_mrk_prc(beg_date,trade_date,[underlying])
        if(len(temp_price)==trading_days):
            temp_price['LOG_PRICE']=np.log(temp_price['UNDERLYING_PRICE'])
            temp_price['RETURN']=temp_price['LOG_PRICE']-temp_price['LOG_PRICE'].shift(1)
            right_return=right_return.append(temp_price)
            modified_right_list.append(underlying)
    portfolio_modified_right=right_portfolio[right_portfolio['UNDERLYING'].isin(modified_right_list)]
    right_price=get_mrk_prc(trade_date,trade_date,modified_right_list)
    portfolio_modified_right=pd.merge( portfolio_modified_right,right_price,on=['TRADE_DATE','UNDERLYING'])
    
    #计算weight
    right_total=(portfolio_modified_right['QUANTITY']*portfolio_modified_right['UNDERLYING_PRICE']).sum()
    portfolio_modified_right['WEIGHT']=portfolio_modified_right['QUANTITY']*portfolio_modified_right['UNDERLYING_PRICE']/right_total
    right_weight= portfolio_modified_right[['UNDERLYING','WEIGHT']]
    
    time_list=list(right_return['TRADE_DATE'].drop_duplicates())
    right_return_list=[]
    for time in time_list:
        right_temp=right_return[right_return['TRADE_DATE']==time]
        right_temp=pd.merge(right_temp,right_weight,on='UNDERLYING')
        right_return_list.append((right_temp['RETURN']*right_temp['WEIGHT']).sum())

    portfolio_return=pd.DataFrame(data=[left_return_list,right_return_list]).T
    
    return portfolio_return.corr().iloc[0,-1]
    
    
    
    
def get_portfolio_ls_requirement_net_exposure(trade_date:str, portfolio:pd.DataFrame) -> float:
    """根据互换新规，计算持仓数据的多空名义本金比例

    :param trade_date: 持仓日期
    :type trade_date: str
    :param portfolio: 持仓数据
    :type portfolio: pd.DataFrame
    :return: 多空名义本金比例
    :rtype: float
    """
    #check
    
    if('COST' in list(portfolio)):
        pass
    else:
        underlying_list=list(portfolio['UNDERLYING'])
        underlying_price=get_mrk_prc(trade_date,trade_date,underlying_list)
        portfolio=pd.merge(portfolio,underlying_price,on=['TRADE_DATE','UNDERLYING'])
        portfolio['COST']=portfolio['QUANTITY']*portfolio['UNDERLYING_PRICE']
        
    net_exposure=portfolio[portfolio['DIRECTION']=='L']['COST'].sum()/portfolio[portfolio['DIRECTION']=='R']['COST'].sum()
    return net_exposure
    

def get_portfolio_ls_requirement_min_margin(beg_date:str,trade_date:str, portfolio:pd.DataFrame) -> float:
    """根据互换新规，如果客户满足多空条件，则需缴纳的最少的保证金额度
    
    :param beg_date: 计算相关性的起始日期
    :type beg_date: str
    :param trade_date: 持仓日期
    :type trade_date: str
    :param portfolio: 持仓数据
    :type portfolio: pd.DataFrame
    :return: 多空保证金额度
    :rtype: float
    """
    #同样先判断客户是否给了成本
    if('COST' in list(portfolio)):
        pass
    else:
        underlying_list=list(portfolio['UNDERLYING'])
        underlying_price=get_mrk_prc(trade_date,trade_date,underlying_list)
        portfolio=pd.merge(portfolio,underlying_price,on=['TRADE_DATE','UNDERLYING'])
        portfolio['COST']=portfolio['QUANTITY']*portfolio['UNDERLYING_PRICE']
     
    portfolio_left=portfolio[portfolio['DIRECTION']=='L']
    portfolio_right=portfolio[portfolio['DIRECTION']=='R']
    
    #查看客户信息是否满足第二类保证金需求    
    if(get_portfolio_ls_requirement_underlying_count(portfolio)['number'].max()>=50 
       and get_portfolio_ls_requirement_cost_weight(trade_date,portfolio)['COST_WEIGHT'].max()<=0.05 
       and get_portfolio_ls_requirement_corr(beg_date,trade_date,portfolio)>=0.8
       and get_portfolio_ls_requirement_net_exposure(trade_date,portfolio)>=0.8
       and get_portfolio_ls_requirement_net_exposure(trade_date,portfolio)<=1.2  ):
        min_margin=max(portfolio_left['COST'].sum(),portfolio_right['COST'].sum())*0.25
        
    else:
        min_margin=max(portfolio_left['COST'].sum(),portfolio_right['COST'].sum())
    
    return min_margin
